package main

import (
	"fmt"
	"gopatch/file"
	"gopatch/git"
	"gopatch/git/handler"
	"gopatch/global"
	"gopatch/patchos"
	fileUtil "gopatch/utils/file"
	"io"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"sort"
	"strings"
	"sync"
	"time"
)

// 准备操作，读取配置信息
func init() {
	log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds | log.Lshortfile)
	global.InitProperty()
	log.Printf("初始化参数完成\n")
}

func main() {

	finalPatchDir := createPatchDir()
	logFile := createPatchFile(finalPatchDir)

	defer logFile.Close()

	defer func() {
		// 异常恢复
		panicErr := recover()
		if panicErr != nil {
			// if errT, ok := err.(error); ok {
			handleError(panicErr)
			// } else {
			//	log.Printf("程序出现异常 %v\n", err)
			// }
		}

		if global.GetMsgCount(global.WARN) > 0 {
			log.Println("\u001B[33m\u001B[1m-----------------------  注意！存在警告信息 -----------------------\u001B[0m")
			log.Println("\u001B[43m----------------------------------------------------------------\u001B[0m")
			log.Println("\n" + global.GetMsg(global.WARN))
			log.Println("\u001B[43m----------------------------------------------------------------\u001B[0m")
		}

		if global.GetMsgCount(global.ERROR) > 0 {
			log.Println("\u001B[31m\u001B[1m-----------------------  注意！存在错误信息 -----------------------\u001B[0m")
			log.Println("\u001B[41m----------------------------------------------------------------\u001B[0m")
			log.Println("\n" + global.GetMsg(global.ERROR))
			log.Println("\u001B[41m----------------------------------------------------------------\u001B[0m")
		}

		log.Printf("\n\t存在错误信息[%d]条, 警告信息[%d]条", global.GetMsgCount(global.ERROR), global.GetMsgCount(global.WARN))
		if panicErr != nil {
			log.Printf("\n\t程序异常中断")
		}

	}()

	log.Printf("读取配置信息property:%#v\n", global.GetProperty())
	log.Printf("以下文件不纳入补丁: %v\n", global.GetNeverPatchFiles())

	// 处理命令行参数，并组装处理得到修订号集合
	gitVersions := git.HandleArgs()

	// 将需要更新和删除的文件去重放入新的集合
	// key-git文件路径，val-git修订号结构体
	// 所有需要更新/新增的文件
	var allUpdateFiles = make(map[string][]git.GitVersionStruct)
	// 所有需要删除的文件，不需要处理修订号结构体
	var allDeleteFiles = make(map[string]struct{})

	for _, gitVersion := range gitVersions {
		for _, updateFile := range gitVersion.GetUpdateFiles() {
			if allUpdateFiles[updateFile] == nil {
				allUpdateFiles[updateFile] = []git.GitVersionStruct{}
			}
			allUpdateFiles[updateFile] = append(allUpdateFiles[updateFile], gitVersion)
		}

		for _, deleteFile := range gitVersion.GetDeleteFiles() {
			allDeleteFiles[deleteFile] = struct{}{}
		}
	}

	// 遍历 target 中所有文件
	allFilesMap := fileUtil.FindAllFilesGo(global.GetProperty().TargetPath)
	// 将文件集合放入全局变量
	global.SetAllFilesMap(allFilesMap)

	// 《核心逻辑》 得到所有需要更新的补丁文件
	allTargetFiles := handler.HandleUpdateFiles(allUpdateFiles)

	log.Printf("需要更新的文件: %d个, \n", len(allTargetFiles))

	log.Println("-------------- 待复制目标路径 -------------")
	for _, absPath := range allTargetFiles {
		log.Printf("------ %s\n", absPath.AbsFilePath)
	}
	log.Println("------------------------------------------")

	// --------------- 开始复制编译后源文件
	patchPath := copyResource(finalPatchDir, allTargetFiles)

	// --------------- 如果有更新的源文件，开始打包
	if len(allTargetFiles) > 0 {
		// 归档补丁，打成 tar 包
		err := fileUtil.Tar(filepath.Join(patchPath, global.GetProperty().PatchName), patchPath)
		if err != nil {
			panic("归档 tar 包出错!" + err.Error())
		}
	}

	// --------------- 写入相关的提交记录信息
	commitMsg := strings.Builder{}
	for _, gitVersion := range gitVersions {
		commitMsg.WriteString(fmt.Sprintf("%s -- %s\n", gitVersion.GitVersion, gitVersion.GetCommitMessage()))
	}
	if commitMsg.Len() > 0 {
		err := os.WriteFile(filepath.Join(patchPath, "相关提交记录.txt"), []byte(commitMsg.String()), 0755)
		if err != nil {
			global.AppendMsg(global.ERROR, fmt.Sprintf("写入相关提交记录的文件报错! %v\n", err))
		}
	}

	// --------------- 写入删除文件的信息
	deleteMsg := strings.Builder{}

	// 排个序
	var sortDeleteMap []string
	for deleteFile := range allDeleteFiles {
		sortDeleteMap = append(sortDeleteMap, deleteFile)
	}
	sort.Strings(sortDeleteMap)

	if len(sortDeleteMap) > 0 {
		deleteMsg.WriteString("可能需要删除的文件:\n")
		for _, delFile := range sortDeleteMap {
			deleteMsg.WriteString(delFile + "\n")
		}

		err := os.WriteFile(filepath.Join(patchPath, "可能需要删除的文件.txt"), []byte(deleteMsg.String()), 0755)
		if err != nil {
			global.AppendMsg(global.ERROR, fmt.Sprintf("写入需要删除的文件报错! %v\n", err))
		}
	}

	log.Printf("尝试打开目录:%s\n", patchPath)
	patchos.OpenOsFolder(patchPath)

}

func copyResource(finalPatchDir string, paths []file.FileStruct) string {
	finalPatchPath := finalPatchDir

	for _, path := range paths {
		fileUtil.CopyFile(path.AbsFilePath, finalPatchPath)
	}

	log.Println("------------- 复制文件完成")

	return finalPatchPath
}

func handleError(err any) {
	if err != nil {
		// 获取当前goroutine的堆栈信息
		stack := make([]byte, 4096)
		n := runtime.Stack(stack, false)
		log.Printf("Error occurred: %v\nStack trace:\n%s\n", err, string(stack[:n]))
	}
}

// MyFile 重写 os.File 的 Write 方法
type MyFile struct {
	file       *os.File         `name:"输出日志文件"`
	delRegexps []*regexp.Regexp `name:"删除字符的正则"`
	lock       sync.Mutex       `name:"上锁"`
}

func (m *MyFile) Write(p []byte) (int, error) {
	m.lock.Lock()
	// 对写入日志文件的记录进行特殊处理
	s := string(p)
	// 根据正则去除字符
	if m.delRegexps != nil && len(m.delRegexps) > 0 {
		for _, re := range m.delRegexps {
			s = re.ReplaceAllString(s, "")
		}
	}
	p = []byte(s)
	n, err := m.file.Write(p)
	m.lock.Unlock()
	return n, err
}

// 输出日志定位到文件，创建补丁文件
func createPatchFile(finalPatchDir string) *os.File {
	// 创建或打开一个文件
	logFilePath := filepath.Join(finalPatchDir, global.GetProperty().PatchName+"_app.log")
	logFile, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	if err != nil {
		log.Fatalf("error opening file: %v", err)
	}

	re, _ := regexp.Compile("(\u001B)?\\[\\d{1,2}m")
	myFile := &MyFile{
		file:       logFile,
		delRegexps: []*regexp.Regexp{re},
		lock:       sync.Mutex{},
	}

	// 创建一个 io.MultiWriter 实例，它允许你将输出写入多个地方
	multiWriter := io.MultiWriter(os.Stdout, myFile)

	time.Sleep(1 * time.Second)

	// 设置日志输出为 multiWriter
	log.SetOutput(multiWriter)

	log.Printf("创建日志文件: %s\n", logFilePath)

	return logFile
}

// 创建补丁目录
func createPatchDir() string {
	patchPath := global.GetProperty().PatchPath

	now := time.Now()
	formatted := now.Format("20060102_150405")
	finalPatchPath := filepath.Join(patchPath, global.GetProperty().PatchName+"-"+formatted)
	err := os.MkdirAll(finalPatchPath, 0755)
	if err != nil {
		panic(fmt.Errorf("创建补丁目录报错!: %v", err.Error()))
	}
	return finalPatchPath
}
